﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

using UnityEngine;

// 挖洞法
public class MazeBuilder : MonoBehaviour {

    [SerializeField]
    private GameObject ground;
    [SerializeField]
    private GameObject wallPrefab;
    [SerializeField]
    private Camera mainCamera;

    private int mazeHeight;
    private int mazeWidth;
    private bool [, ] maze;
    private GameObject [, ] mazeObject;
    private List<Vector2> searchDirections = new List<Vector2> { Vector2.up, Vector2.down, Vector2.right, Vector2.left };

    private void Start ( ) {
        CreateMaze (21, 21);
    }

    private void CreateMaze (int height, int width) {
        mazeWidth = width;
        mazeHeight = height;

        InitializeMaze ( );

        StartCoroutine (DigMaze ( ));
    }

    private void InitializeMaze ( ) {
        maze = new bool [mazeWidth, mazeHeight];
        mazeObject = new GameObject [mazeWidth, mazeHeight];
        for (int i = 0; i < mazeWidth; i++) {
            for (int j = 0; j < mazeHeight; j++) {
                maze [i, j] = true;
                mazeObject [i, j] = Instantiate (wallPrefab, new Vector3 (i, 0, j), Quaternion.identity) as GameObject;
            }
        }

        var centered = new Vector3 (mazeWidth / 2f - 0.5f, 0, mazeHeight / 2f - 0.5f);
        ground.transform.localScale = new Vector3 (mazeWidth, 1, mazeHeight);
        ground.transform.position = centered + Vector3.down * 0.5f;

        mainCamera.transform.position = centered + Vector3.up * ((mazeWidth + mazeHeight) / 2f);
    }

    private IEnumerator DigMaze ( ) {
        int startPosW = Enumerable.Range (0, mazeWidth).Where (i => i % 2 != 0).OrderBy (i => Guid.NewGuid ( )).First ( );
        int startPosH = Enumerable.Range (0, mazeHeight).Where (i => i % 2 != 0).OrderBy (i => Guid.NewGuid ( )).First ( );
        yield return Dig (new Vector2 (startPosW, startPosH));
    }

    private IEnumerator Dig (Vector2 point) {
        yield return RemoveWall (point);

        foreach (var dir in searchDirections.OrderBy (i => Guid.NewGuid ( ))) {
            var checkPos = point + dir * 2;
            if (IsInBoard (checkPos) && maze [(int) checkPos.x, (int) checkPos.y]) {
                yield return RemoveWall (point + dir);
                yield return Dig (checkPos);
            }
        }
    }

    private IEnumerator RemoveWall (Vector2 point) {
        var w = (int) point.x;
        var h = (int) point.y;
        maze [w, h] = false;
        Destroy (mazeObject [w, h]);
        yield return new WaitForSeconds (0.05f);
    }

    private bool IsInBoard (Vector2 pos) {
        return pos.x >= 0 && pos.y >= 0 && pos.x < mazeWidth && pos.y < mazeHeight;
    }
}
